from langchain_core.runnables import RunnableLambda



from langchain_core.runnables import RunnableBranch


from langchain_core.runnables import ConfigurableField

from langchain_openai import ChatOpenAI
from langchain.output_parsers import PandasDataFrameOutputParser, OutputFixingParser
from langchain_core.prompts import PromptTemplate
import pandas as pd

from langchain_core.example_selectors import (
    LengthBasedExampleSelector,
    MaxMarginalRelevanceExampleSelector,
    SemanticSimilarityExampleSelector,
)
from langchain_core.prompts import (
    AIMessagePromptTemplate,
    BaseChatPromptTemplate,
    BasePromptTemplate,
    ChatMessagePromptTemplate,
    ChatPromptTemplate,
    FewShotChatMessagePromptTemplate,
    FewShotPromptTemplate,
    FewShotPromptWithTemplates,
    HumanMessagePromptTemplate,
    MessagesPlaceholder,
    PipelinePromptTemplate,
    PromptTemplate,
    StringPromptTemplate,
    SystemMessagePromptTemplate,
    load_prompt,
)

from langchain._api import create_importer
from langchain.prompts.prompt import Prompt

llm = ChatOpenAI(
    model="deepseek-chat",
    temperature=0,
    openai_api_key="sk-605e60a1301040759a821b6b677556fb",
    base_url="https://api.deepseek.com/v1")


from langchain_core.chat_models.openai import ChatOpenAI
from langchain_core.chat_models.anthropic import ChatAnthropic

model = ChatAnthropic(model="claude-3-haiku-20240307").with_fallbacks(
[ChatOpenAI(model="gpt-3.5-turbo-0125")]
)
# Will usually use ChatAnthropic, but fallback to ChatOpenAI
# if ChatAnthropic fails.
model.invoke("hello")

# And you can also use fallbacks at the level of a chain.
# Here if both LLM providers fail, we'll fallback to a good hardcoded
# response.

from langchain_core.prompts import PromptTemplate
from langchain_core.output_parser import StrOutputParser
from langchain_core.runnables import RunnableLambda

from typing import Optional

from langchain_community.chat_models import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory

from typing import TYPE_CHECKING

from langchain_core._import_utils import import_attr

if TYPE_CHECKING:
    from langchain_core.runnables.base import (
        Runnable,
        RunnableBinding,
        RunnableGenerator,
        RunnableLambda,
        RunnableMap,
        RunnableParallel,
        RunnableSequence,
        RunnableSerializable,
        chain,
    )
    from langchain_core.runnables.branch import RunnableBranch
    from langchain_core.runnables.config import (
        RunnableConfig,
        ensure_config,
        get_config_list,
        patch_config,
        run_in_executor,
    )
    from langchain_core.runnables.fallbacks import RunnableWithFallbacks
    from langchain_core.runnables.history import RunnableWithMessageHistory
    from langchain_core.runnables.passthrough import (
        RunnableAssign,
        RunnablePassthrough,
        RunnablePick,
    )
    from langchain_core.runnables.router import RouterInput, RouterRunnable
    from langchain_core.runnables.utils import (
        AddableDict,
        ConfigurableField,
        ConfigurableFieldMultiOption,
        ConfigurableFieldSingleOption,
        ConfigurableFieldSpec,
        aadd,
        add,
    )


from langchain_core.runnables import (
    RunnableLambda,
    RunnableParallel,
    RunnablePassthrough,
)

from langchain_core.runnables import RunnablePassthrough


def fake_llm(prompt: str) -> str:  # Fake LLM for the example
    return "completion"


runnable = {
    "llm1": fake_llm,
    "llm2": fake_llm,
} | RunnablePassthrough.assign(
    total_chars=lambda inputs: len(inputs["llm1"] + inputs["llm2"])
)

rs=runnable.invoke("hello")
print(rs)
# {'llm1': 'completion', 'llm2': 'completion', 'total_chars': 20}



'''
runnable = RunnableParallel(
    origin=RunnablePassthrough(), modified=lambda x: x + 1
)

rs=runnable.invoke(1)  # {'origin': 1, 'modified': 2}
print(rs)


def get_by_session_id(session_id: str) -> BaseChatMessageHistory:
            if session_id not in store:
                store[session_id] = InMemoryHistory()
            return store[session_id]

prompt = ChatPromptTemplate.from_messages(
[
    ("system", "You're an assistant who's good at {ability}"),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{question}"),
]
)

chain = prompt | llm

chain_with_history = RunnableWithMessageHistory(
chain,

input_messages_key="question",
history_messages_key="history",
)

print(
chain_with_history.invoke(  # noqa: T201
    {"ability": "math", "question": "What does cosine mean?"},
    config={"configurable": {"session_id": "foo"}},
)
)

# Uses the store defined in the example above.
print(store)  # noqa: T201

print(
chain_with_history.invoke(  # noqa: T201
    {"ability": "math", "question": "What's its inverse"},
    config={"configurable": {"session_id": "foo"}},
)
)

print(store)  # noqa: T201




def when_all_is_lost(inputs):
    return (
        "Looks like our LLM providers are down. "
        "Here's a nice 🦜️ emoji for you instead."
    )


chain_with_fallback = (
                  PromptTemplate.from_template("Tell me a joke about {topic}")
                | model
                | StrOutputParser()
                ).with_fallbacks([RunnableLambda(when_all_is_lost)])

chain_with_fallback



# This creates a RunnableConfigurableAlternatives for Prompt Runnable
# with two alternatives.
prompt = PromptTemplate.from_template(
"Tell me a joke about {topic}"
).configurable_alternatives(
ConfigurableField(id="prompt"),
default_key="joke",
poem=PromptTemplate.from_template("Write a short poem about {topic}"),
)

# When invoking the created RunnableSequence, you can pass in the
# value for your ConfigurableField's id which in this case will either be
# `joke` or `poem`.
chain = prompt | llm

# The `with_config` method brings in the desired Prompt Runnable in your
# Runnable Sequence.
rs=chain.with_config(configurable={"s": "poem"}).invoke({"topic": "bears"})
#rs=chain.with_config(configurable={"prompt": "poem"}).invoke({"topic": "bears"})

print(rs)


branch = RunnableBranch(
    (lambda x: isinstance(x, str), lambda x: x.upper()),
    (lambda x: isinstance(x, int), lambda x: x + 1),
(lambda x: isinstance(x, float), lambda x: x * 2),
lambda x: "goodbye",
)

rs=branch.invoke("hello")  # "HELLO"
print(rs)
branch.invoke(None)  # "goodbye"

# A RunnableSequence constructed using the `|` operator
sequence = RunnableLambda(lambda x: x + 1) | RunnableLambda(lambda x: x * 2)
sequence.invoke(1)  # 4
sequence.batch([1, 2, 3])  # [4, 6, 8]


# A sequence that contains a RunnableParallel constructed using a dict literal
sequence = RunnableLambda(lambda x: x + 1) | {
"mul_2": RunnableLambda(lambda x: x * 2),
"mul_5": RunnableLambda(lambda x: x * 5),
}
rs=sequence.invoke(1)  # {'mul_2': 4, 'mul_5': 10}
print(rs)
'''